﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using Stub.GoogleJwt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Stub.Jwt
{
    public enum JwtHashAlgorithm
    {
        RS256,
        HS384,
        HS512
    }

    /// <summary>
    ///  Generates a JWT Purchase Token
    ///  JWT v1 and HMAC SHA-256 signatures
    /// </summary>
    public class JsonWebToken
    {

        private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;

        public JsonWebToken()
        {
            HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
            {
                { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
                { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
            };
        }
        string _head = null;
        string _payload = null;
        string _signature = null;
        public JsonWebToken(String Jwt)
            : this()
        {
            string[] split = Jwt.Split('.');

            _head = Utility.Base64UrlDecode(split[0]);
            _payload = Utility.Base64UrlDecode(split[1]);
            _signature = split[2];

        }

        public string Generate(String HeadJWT, String ContentJWT, string SigningKey)
        {
            return Generate(HeadJWT, ContentJWT, Utility.getBytes(SigningKey));
        }
        public string Generate(String HeadJWT, String ContentJWT, byte[] SigningKey)
        {
            _head = HeadJWT;
            _payload = ContentJWT;

            // base64 Url Encode Head
            var bytes = Utility.getBytes(HeadJWT);
            var base64Head = Utility.Base64UrlEncode(bytes);

            // base64 Url Encode Payload (Json Content)
            bytes = Utility.getBytes(ContentJWT);
            var base64Payload = Utility.Base64UrlEncode(bytes);

            // sign it
            var secureInputValue = String.Format("{0}.{1}", base64Head, base64Payload);
            bytes = Stub.Jwt.Utility.getBytes(secureInputValue);
            bytes = Stub.Jwt.Utility.ComputeHMACSha265(bytes, SigningKey);
            _signature = Stub.Jwt.Utility.Base64UrlEncode(bytes);

            return String.Format("{0}.{1}.{2}", base64Head, base64Payload, _signature);
        }

        private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
        {
            switch (algorithm)
            {
                case "RS256": return JwtHashAlgorithm.RS256;
                case "HS384": return JwtHashAlgorithm.HS384;
                case "HS512": return JwtHashAlgorithm.HS512;
                default: throw new InvalidOperationException("Algorithm not supported.");
            }
        }


        public string Jwt
        {
            get
            {
                return String.Format("{0}.{1}.{2}", _head, _payload, _signature);
            }
        }

        public string Signature
        {
            get { return _signature; }
        }
        public string PayLoad
        {
            get { return _payload; }
        }
        public string Head
        {
            get { return _head; }
        }

        public bool Valid(String Key)
        {
            var jwt = new JsonWebToken();
            var j = jwt.Generate(_head, _payload, Key);

            return jwt.Signature.Equals(this.Signature);
        }
    }

    public static class Utility
    {
        public static byte[] getBytes(String content)
        {
            return Encoding.UTF8.GetBytes(content);
        }


        public static byte[] ComputeHMACSha265(byte[] Content, byte[] SigningKey)
        {
            HMACSHA256 sha;
            if (!(SigningKey == null))
                sha = new HMACSHA256(SigningKey);
            else
                sha = new HMACSHA256();

            return sha.ComputeHash(Content);

        }

        // from JWT spec
        public static string Base64UrlEncode(byte[] input)
        {
            var output = Convert.ToBase64String(input);
            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-'); // 62nd char of encoding
            output = output.Replace('/', '_'); // 63rd char of encoding
            return output;
        }

        // from JWT spec
        public static string Base64UrlDecode(string input)
        {
            var output = input;
            output = output.Replace('-', '+'); // 62nd char of encoding
            output = output.Replace('_', '/'); // 63rd char of encoding
            switch (output.Length % 4) // Pad with trailing '='s
            {
                case 0: break; // No pad chars in this case
                case 2: output += "=="; break; // Two pad chars
                case 3: output += "="; break; // One pad char
                default: throw new System.Exception("Illegal base64url string!");
            }
            var converted = Convert.FromBase64String(output); // Standard base64 decoder
            return Encoding.UTF8.GetString(converted);
        }

    }
}
